package tmt.usercenter.web.domain;

import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.tmt.annotation.*;
import com.tmt.jpafieldvalueconvert.JpaFieldValueConvert;
import com.tmt.jpafieldvalueconvert.JpaFieldValueConvertListener;
import com.tmt.jpafieldvalueconvert.impl.FileUrlFieldConverter;
import com.voodoodyne.jackson.jsog.JSOGGenerator;
import lombok.Getter;
import lombok.Setter;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import tmt.usercenter.web.domain.converter.StringAndListConverter;
import tmt.usercenter.web.domain.enums.AuthorityType;
import tmt.usercenter.web.domain.enums.OperationType;
import tmt.usercenter.web.domain.vo.MenuItem;

import javax.persistence.*;
import java.io.Serializable;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 系统内所有权限
 * Created by Stark on 2017/3/10.
 */
@Getter
@Setter
@EntityListeners({JpaFieldValueConvertListener.class, AuditingEntityListener.class})
@JsonIdentityInfo(generator = JSOGGenerator.class)
@Entity
@JsonIgnoreProperties(value = {"hibernateLazyInitializer", "handler", "fieldHandler"})

@LazyLoadDefine({
        @LazyLoadMapping(name = "1", value = {"parent", "originSystem"})
})
public class TmtAuthority extends BaseCreator implements Serializable {

    @Select2OptionItem
    @Column(nullable = false)
    private String name;

    @Select2OptionItem(order = 1)
    @Column(nullable = false)
    private String authority;

    @Column(nullable = false)
    private String description;

    @Column(nullable = false, columnDefinition = "TINYINT(1)")
    private boolean visible = true;

    @Enumerated(EnumType.STRING)
    private AuthorityType authorityType = AuthorityType.CUSTOM;

    @Enumerated(EnumType.STRING)
    private OperationType operationType = OperationType.MENU;

    /**
     * 对于自定义类型的权限，本字段保存了受控的访问点列表
     */
    @Convert(converter = StringAndListConverter.class)
    private List<String> accessPoint;

    /**
     * 权限图标
     */
    @JpaFieldValueConvert(converter = FileUrlFieldConverter.class, displayName = "权限图标")
    @SyncLoadFile
    @Select2OptionAttr("icon")
    @Column
    private String icon;

    /**
     * 显示排序
     */
    @Select2OptionAttr("sort")
    @Column(nullable = false)
    private int sort = 0;

    /**
     * 第三方应用为权限记录设置的标记，例如：可以通过设置本字段对权限或菜单进行分类。
     * TMT用户中心将本字段作为前后台菜单分类用
     */
    @Column
    private String clientTag;

    /**
     * 权限所属的父级权限对象。
     */
    @Select2OptionAttr(value = "parent.id", isParent = true)
    @Select2OptionValue("parent.id")
    @ManyToOne(cascade = {CascadeType.REFRESH}, fetch = FetchType.LAZY)
    @JoinColumn(name = "parent_id")
    private TmtAuthority parent;

    /**
     * 权限所属的子级权限集合。
     */
    @OneToMany(mappedBy = "parent", cascade = {CascadeType.REFRESH})
    private List<TmtAuthority> children;

    /**
     * 权限所属的客户系统。
     */
    @Select2OptionValue("originSystem.id")
    @ManyToOne(cascade = {CascadeType.REFRESH}, fetch = FetchType.LAZY)
    @JoinColumn(name = "originSystem_id")
    private ClientSystem originSystem;

    @OneToMany(mappedBy = "authority", cascade = {CascadeType.REFRESH})
    private List<TmtRoleAuthority> roleAuthorities;

    @Transient
    private String parentCode;

    @Transient
    private String clientId;

    /**
     * 是否是当前选中的菜单项
     */
    @Transient
    private boolean active;

    /**
     * 获取有效的树形菜单列表。
     *
     * @param uri                 当前HttpServletRequset对象的Uri值
     * @param servletContextPath  当前HttpServletRequset对象的contextPath值
     * @param tmtAuthorities      全部权限记录列表
     * @param clientId            要过滤出来的权限记录的ClientId字段值
     * @param clientTags          要过滤出来的权限记录的ClientTag字段值
     * @param disabledAuthorities 不希望显示的菜单权限。
     * @param paramMap            希望给菜单链接追加的参数。
     * @return
     */
    public static List<MenuItem> getValidMenuTree(String uri,
                                                  String servletContextPath,
                                                  List<TmtAuthority> tmtAuthorities,
                                                  String clientId,
                                                  List<String> clientTags,
                                                  List<String> disabledAuthorities,
                                                  Map<String, String> paramMap) {
        if (tmtAuthorities == null || tmtAuthorities.size() <= 0)
            return new ArrayList<>();

        //选出顶级菜单项,并初始化菜单项的顺序。
        List<TmtAuthority> list = tmtAuthorities.stream()
                .filter(x -> x.getParent() == null && x.isVisible()
                        && x.getOperationType() == OperationType.MENU
                        && (x.getOriginSystem() != null && x.getOriginSystem().getClientId().equals(clientId)
                        || x.getClientId() != null && x.getClientId().equals(clientId))
                        && x.getClientTag() != null && clientTags.stream().anyMatch(y -> x.getClientTag().contains(y))
                ).sorted(Comparator.comparingInt(TmtAuthority::getSort))
                .collect(Collectors.toList());

        List<MenuItem> menuItemList = new ArrayList<>();
        List<TmtAuthority> tmpList = list;
        //逐层加载子菜单项
        while (tmpList.size() > 0) {
            List<TmtAuthority> list1 = new ArrayList<>();
            for (TmtAuthority auth : tmpList) {

                //仅仅处理菜单项类型
                if (auth.getOperationType() != OperationType.MENU)
                    continue;

                //创建菜单项对象。
                MenuItem menuItem = new MenuItem(auth.getId(), auth.getName(), auth.getAuthority(), auth.getIcon(),
                        auth.isVisible(), auth.isActive(), null, auth.getAccessPoint());
                menuItemList.add(menuItem);

                //将菜单项插入其父菜单的子项列表中。
                if (auth.getParent() != null) {
                    Optional<MenuItem> optional = menuItemList.stream().filter(z -> Objects.equals(z.getId(), auth.getParent().getId())).findFirst();
                    optional.ifPresent(z -> {
                        menuItem.setParent(z);
                        z.getChildren().add(menuItem);
                    });
                }

                //根据uri判断当前选中的菜单项。
                menuItem.setActive(false);
                if (menuItem.getAccessPoint() != null && menuItem.getAccessPoint().size() > 0) {
                    String accessPoint = menuItem.getAccessPoint().get(0);
                    accessPoint = accessPoint.split("\\?")[0].replaceFirst(servletContextPath + "/", "/");
                    String tmpUri = uri.split("\\?")[0].replaceFirst(servletContextPath + "/", "/");
                    boolean active = tmpUri.startsWith(accessPoint);
                    menuItem.setActive(active);
                }

                //设置不显示的菜单项。
                if (menuItem.isVisible() && disabledAuthorities != null
                        && disabledAuthorities.stream().anyMatch(z -> Objects.equals(z, auth.getAuthority()))) {
                    menuItem.setVisible(false);
                }

                //为菜单目标链接增加servletContextPath路径。
                List<String> accessPoints = new ArrayList<>();
                menuItem.getAccessPoint().forEach(y -> {
                    if (y != null && y.trim().length() > 0 && !y.trim().startsWith(servletContextPath + "/"))
                        y = servletContextPath + y;
                    //为链接追加参数
                    if (paramMap != null && paramMap.size() > 0)
                        y = appendParameter(y, paramMap);
                    accessPoints.add(y);
                });
                menuItem.setAccessPoint(accessPoints);

                //设置菜单项的获取子菜单,并控制子菜单项的顺序。
                List<TmtAuthority> children = tmtAuthorities.stream()
                        .filter(y -> y.getParent() != null && Objects.equals(y.getParent().getId(), auth.getId()))
                        .sorted(Comparator.comparingInt(TmtAuthority::getSort))
                        .collect(Collectors.toList());
                list1.addAll(children);
            }
            tmpList = list1;
        }

        //将Active=true的项的父级项都设置为真；
        List<MenuItem> activeItems = menuItemList.stream().filter(MenuItem::isActive).collect(Collectors.toList());
        activeItems.forEach(x -> {
            MenuItem parent = x.getParent();
            while (parent != null) {
                parent.setActive(true);
                parent = parent.getParent();
            }
        });

        return menuItemList.stream().filter(x -> x.getParent() == null).collect(Collectors.toList());
    }


    private static String appendParameter(String url, Map<String, String> paramMap) {
        if (paramMap == null)
            return url;

        Map<String, String> map = new HashMap<>();

        String[] queryString = url.split("\\?");
        if (queryString.length == 2 && queryString[1].length() > 0) {
            String params = queryString[1];
            Arrays.asList(params.split("&")).forEach(x -> {
                String[] infos = x.split("=");
                if (!paramMap.containsKey(infos[0]) && infos.length == 2) {
                    map.put(infos[0], infos[1]);
                }
            });
        }

        paramMap.keySet().forEach(x -> map.put(x, paramMap.get(x)));

        queryString[0] = queryString[0] + "?";
        map.keySet().forEach(x -> {
            queryString[0] += ("&" + x + "=" + map.get(x));
        });

        queryString[0] = queryString[0].replace("?&", "?");

        return queryString[0];
    }

}
